﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swcommands;
using SolidWorks.Interop.swconst;
using SolidWorks.Interop.swpublished;

using SwAddinHelpers.Contracts;
using SwAddinHelpers.ComHelpers;
using SwAddinHelpers.Attributes;

using System.Reflection;
using System.Reflection.Emit;

namespace SwAddinHelpers.Sinks
{
    //http://msdn.microsoft.com/en-us/library/system.reflection.emit.aspx
    //http://msdn.microsoft.com/en-us/library/system.reflection.emit.methodrental.swapmethodbody.aspx
    //http://msdn.microsoft.com/en-us/library/system.reflection.emit.modulebuilder.aspx
       
    [ComVisible(true)]
    public class CallbackPool: AddinProxy 
    {
        private object _callbackTarget = null;
        public object CallbackTarget
        {
            get
            {
                if (_callbackTarget == null)
                {
                    _bakedType = _typeBuilder.CreateType();
                    _callbackTarget = Activator.CreateInstance(_bakedType);
                    _callbackTarget.GetType().GetField("OwnerAddin").SetValue(_callbackTarget, _OwnerAddin);
                    // TODO: is this going to mess with COM ?
                }

                return _callbackTarget;
            }
        }

        private Guid _guid;
        private AssemblyBuilder _asmBuilder;
        private ModuleBuilder _modBuilder;
        private TypeBuilder _typeBuilder;
        private Type _bakedType;

        private bool _allowInject = false;

        public CallbackPool(ISwAddinBase owner)
        {
            _OwnerAddin = owner;

            _guid = Guid.NewGuid();
            AssemblyName asmname = new AssemblyName();
            asmname.Name = "callbackProxyAssembly" + _guid;
            //_appDomain = AppDomain.CreateDomain("callbackProxyDomain" + _guid);
            //_asmBuilder = _appDomain.DefineDynamicAssembly(asmname, AssemblyBuilderAccess.Run);
            _asmBuilder = System.Threading.Thread.GetDomain().DefineDynamicAssembly(asmname, AssemblyBuilderAccess.Run);
            _modBuilder = _asmBuilder.DefineDynamicModule("callbackProxyModule");
            _typeBuilder = _modBuilder.DefineType("callbackProxyType", TypeAttributes.Public, null, new Type[] { typeof(IDispatch3) });

            InjectISwAddinBase();

            InjectGeneralPurposeCallback();

            _allowInject = true;

            #region testing
            /*
           //DynamicMethod m = new DynamicMethod("Dynamic1", typeof(void), new Type[] { typeof(string) }, typeof(CallbackPool));
            
           MethodInfo mi = typeof(System.Diagnostics.Debug).GetMethod("WriteLine", new Type[] { typeof(string) });
            
           DynamicMethod m = new DynamicMethod("Dynamic1", typeof(void), null, typeof(CallbackPool));
            
            ILGenerator il = m.GetILGenerator(256);
            //il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ldstr, "hello_dynamic");
            il.EmitCall(OpCodes.Call, mi, null);
            il.Emit(OpCodes.Ret);

            dd = (DynamicDelegate)m.CreateDelegate(typeof(DynamicDelegate));
            dd.Invoke();
            */
            #endregion
        }

        public void Destroy()
        {
            _OwnerAddin.SolidWorksApplication.RemoveCallback(_OwnerAddin.Cookie);
            _OwnerAddin.SolidWorksApplication.SetAddinCallbackInfo(0, new object(), _OwnerAddin.Cookie);
            _callbackTarget = null;
        }

        private void InjectISwAddinBase()
        {
            FieldBuilder fb = _typeBuilder.DefineField("OwnerAddin" , typeof(ISwAddinBase),FieldAttributes.Public | FieldAttributes.Static);
        }

        private void InjectGeneralPurposeCallback()
        {
            MethodBuilder mb = _typeBuilder.DefineMethod(
                "GeneralPurposeCallback", 
                MethodAttributes.Public,
                typeof(int),
                new Type[] { typeof(Int32), typeof(Int32), typeof(Object) });

            ILGenerator il = mb.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Call, this.GetType().GetMethod("GeneralPurposeCallback"));
            il.Emit(OpCodes.Ret);
        }

        public void InjectCommandProxyMethod(MethodInfo targetMethod)
        {
            if (_allowInject)
            {
                MethodBuilder mb = _typeBuilder.DefineMethod(targetMethod.Name, MethodAttributes.Public, targetMethod.ReturnType, null);
                ILGenerator il = mb.GetILGenerator();

                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, targetMethod);
                il.Emit(OpCodes.Ret);
            }
        }

        public void InjectMenuProxyMethod(MethodInfo targetMethod)
        {
            if (_allowInject)
            {
                MethodBuilder mb = _typeBuilder.DefineMethod(targetMethod.Name, MethodAttributes.Public, targetMethod.ReturnType, null);
                ILGenerator il = mb.GetILGenerator();

                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, targetMethod);
                il.Emit(OpCodes.Ret);
            }
        }
        
        public int GeneralPurposeCallback(int commandId, int data, object notUsed)
        {
            swAppCallBackCmd_e typedCmdId = (swAppCallBackCmd_e)commandId;
            return 0;
        }
    }
    
}

